import { director, AudioSource, Node, NodeEventType, Camera, Vec2, Vec3, geometry, PhysicsSystem, EventTouch, EventMouse, log, v3, UIOpacity, tween, UITransform, sp, Animation, AnimationClip } from 'cc';
import Random from './MathUtils';
import { BundleManager } from '../managers/BundleManager';

// 导出方向枚举
export enum MoveDirection {
    Up = 'up',
    Down = 'down',
    Left = 'left',
    Right = 'right',
    LeftUp = 'leftUp',
    RightUp = 'rightUp',
    LeftDown = 'leftDown',
    RightDown = 'rightDown',
}

export class CommonUtils {
    /**
     * 暂停引擎
     * @example
     * CommonUtils.pauseEngine();
     */
    public static pauseEngine(): void {
        director.pause();
    }

    /**
     * 恢复引擎
     * @example
     * CommonUtils.resumeEngine();
     */
    public static resumeEngine(): void {
        director.resume();
    }

    /**
     * 暂停所有音效
     * @example
     * CommonUtils.pauseAllAudio(rootNode);
     */
    public static pauseAllAudio(root: Node): void {
        root.getComponentsInChildren(AudioSource).forEach(audio => audio.pause());
    }

    /**
     * 恢复所有音效
     * @example
     * CommonUtils.resumeAllAudio(rootNode);
     */
    public static resumeAllAudio(root: Node): void {
        root.getComponentsInChildren(AudioSource).forEach(audio => audio.play());
    }

    /**
     * 递归获取所有子节点，并以节点名为 key 返回对象，支持同名节点自动加后缀
     * @example
     * const nodeMap = CommonUtils.getAllChildrenMap(rootNode);
     * nodeMap.targetName.active = true;
     * nodeMap['targetName_1'].active = false;
     */
    public static getAllChildrenMap(node: Node): { [name: string]: Node } {
        const result: { [name: string]: Node } = {};
        const nameCount: { [name: string]: number } = {};

        function traverse(n: Node) {
            n.children.forEach(child => {
                let key = child.name;
                if (result[key]) {
                    nameCount[key] = (nameCount[key] || 0) + 1;
                    key = `${key}_${nameCount[key]}`;
                } else {
                    nameCount[key] = 0;
                }
                result[key] = child;
                traverse(child);
            });
        }
        traverse(node);
        return result;
    }

    /**
     * 根据子节点名显示某个子节点，隐藏同级其他节点
     * @example
     * CommonUtils.showChildByName(parentNode, 'targetName');
     */
    public static showChildByName(parent: Node, name: string): void {
        parent.children.forEach(child => {
            child.active = (child.name == name);
        });
    }

    /**
     * 2D 节点点击检测（添加点击事件）
     * @example
     * CommonUtils.add2DClick(node, (event) => { ... });
     */
    public static add2DClick(node: Node, callback: (event: EventTouch | EventMouse) => void,scale = 0.9): void {
        node.off(NodeEventType.TOUCH_END)
        node.scale = v3(1, 1, 1)
        node.on(NodeEventType.TOUCH_START, () => {
            node.scale = v3(scale, scale, 1)
        });
        node.on(NodeEventType.TOUCH_END, (event) => {
            node.scale = v3(1, 1, 1)
            callback(event)
        });
    }

    /**
     * 获取当前场景的主相机（第一个 Camera 组件）
     */
    public static getMainCamera(): Camera | null {
        const scene = director.getScene();
        if (!scene) return null;
        let camera: Camera | null = null;
        scene.walk((node: Node) => {
            const cam = node.getComponent(Camera);
            if (cam && !camera) {
                camera = cam;
            }
        });
        return camera;
    }

    /**
     * 3D 节点点击检测（射线检测，需物理系统开启）
     * @example
     * CommonUtils.add3DClick(event, (node) => { ... });
     * @param event 触摸或鼠标事件
     * @param callback 回调，返回被点击的节点或 null
     * @param camera 可选，指定相机，不传则自动查找主相机
     */
    public static add3DClick(event: EventTouch | EventMouse, callback: (node: Node | null) => void, camera?: Camera) {
        if (!camera) {
            camera = this.getMainCamera();
            if (!camera) {
                callback(null);
                return;
            }
        }
        // 获取屏幕坐标
        let screenPos: Vec2;
        if (event instanceof EventTouch) {
            screenPos = event.getLocation();
        } else if (event instanceof EventMouse) {
            screenPos = new Vec2(event.getLocationX(), event.getLocationY());
        } else {
            callback(null);
            return;
        }
        // 屏幕坐标转世界射线
        const ray = camera.screenPointToRay(screenPos.x, screenPos.y);
        // 物理射线检测
        if (PhysicsSystem.instance.raycast(ray)) {
            const results = PhysicsSystem.instance.raycastResults;
            if (results.length > 0) {
                callback(results[0].collider.node);
                return;
            }
        }
        callback(null);
    }

    /**
     * 递归查找子节点（按名称）
     * @example
     * const node = CommonUtils.findChildByName(rootNode, 'targetName');
     */
    public static findChildByName(node: Node, name: string): Node | null {
        for (const child of node.children) {
            if (child.name === name) return child;
            const found = this.findChildByName(child, name);
            if (found) return found;
        }
        return null;
    }

    /**
     * 递归设置节点及其所有子节点的 active 状态
     * @example
     * CommonUtils.setActiveRecursively(rootNode, false);
     */
    public static setActiveRecursively(node: Node, active: boolean): void {
        node.active = active;
        node.children.forEach(child => this.setActiveRecursively(child, active));
    }

    /**
     * 判断节点是否在场景树中
     * @example
     * CommonUtils.isInScene(node);
     */
    public static isInScene(node: Node): boolean {
        let cur: Node | null = node;
        while (cur) {
            if (cur.parent == null) return false;
            if (cur.parent.name === 'Scene') return true;
            cur = cur.parent;
        }
        return false;
    }

    public static isNewDay(timestamp: number): boolean {
        if (timestamp == null) timestamp = 0;
        let date = new Date(timestamp);
        let curDate = new Date();
        if (date.getFullYear() != curDate.getFullYear() || date.getMonth() != curDate.getMonth() || date.getDate() != curDate.getDate()) return true;
        return false;
    }

    /**
    * 将数字转换为带有单位的字符串形式。
    * 主要用于将大数字转换为更易读的格式，如万、亿等单位。
    * 
    * @param num 要转换的数字。
    * @param _maxFixed 小数点保留的最大位数，默认为4。
    * @returns 返回一个对象，包含转换后的数字字符串和对应的单位字符串。
    * @throws 如果输入的不是数字，将抛出错误。
    */
    public static numToString(num: number, _maxFixed: number = 4): { num: string, unit: string } {
        // 检查num是否为合法的数字
        // 校验输入是否为合法数字
        if (typeof num !== 'number' || isNaN(num)) {
            throw new Error("输入必须为数字");
        }

        // 数字小于10000直接返回其字符串形式，单位为空
        // 对于小于10000的情况直接返回
        if (num < 10000) {
            return { num: num.toString(), unit: "" };
        }

        // 定义单位列表，用于大数字的分组表示
        const unitList: Array<string> = ["", "万", "亿", "兆", "京", "垓", "姊", "穰", "沟", "涧", "正", "载", "极", "恒河沙", "阿僧祇", "那由他", "不思议", "无量", "大数", "天数", "无极", "弹指", "无间", "须臾", "虚", "仄", "净", "瞬息", "漠", "坞", "灵", "玉", "晶", "砂", "髓", "魄", "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥", "乾", "坤", "震", "坎", "离", "艮", "兑", "天魁", "天罡", "天机", "天闲", "天勇", "天雄", "天孤", "天伤", "天玄", "天健", "天暗", "天佑", "天空", "天速"]

        // 初始化归一化后的数字和单位索引
        let normalizedNumber: number = num;
        let unitIndex: number = 0;

        // 循环将数字归一化至小于10000，同时确定对应的单位
        while (normalizedNumber >= 10000 && unitIndex < unitList.length) {
            normalizedNumber /= 10000;
            unitIndex++;
        }

        // 初始化最大保留小数位数
        let maxFixed: number = _maxFixed;

        // 生成maxFixed数量的0组成的字符串，用于后续判断是否需要保留小数
        let zeroStr: string = "";
        for (var i = 0; i < maxFixed; i++) {
            zeroStr += "0";
        }

        // 将归一化后的数字转换为字符串形式，保留小数点后maxFixed位
        let fullNumberStr: string = normalizedNumber.toFixed(maxFixed);
        // 将数字字符串按小数点分割
        let splitNumber: string[] = fullNumberStr.split(".");

        // 获取对应的单位
        let unit: string = unitList[unitIndex];

        // 判断是否需要保留小数部分
        let numberStr: string = unitIndex === 0 ? splitNumber[0] : (splitNumber[1].endsWith(zeroStr) ? splitNumber[0] : fullNumberStr);

        // 返回转换后的数字字符串和单位字符串
        return { num: numberStr, unit: unit };
    }

    /**随机数组 */
    public static sortArr(arr: Array<any>) {
        arr.sort((a, b) => Random.floatRange(0, 1) > 0.5 ? 1 : -1);
    }

    /**
    * 生成用户ID,时间戳字符干扰,尽量避免重复
    * @param _num 干扰位数
    * @returns 
    */
    public static getUserId(_num: number): string {
        // 定义可用字符集，包含数字和字母
        let STRMD: string = "1234567890abcdefghijklmnopqrstuvwsyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        // 获取当前时间戳字符串
        let _str: string = Date.now() + "";
        // 将时间戳字符串拆分为数组
        let tArr: Array<string> = [];
        for (var i = 0; i < _str.length; i++) {
            tArr.push(_str[i]);
        }
        // 向数组中添加指定数量的随机字符，以增加ID的复杂度和唯一性
        for (var i = 0; i < _num; i++) {
            tArr.push(STRMD[Math.floor(Math.random() * STRMD.length)]);
        }
        // 对数组进行随机排序，进一步增加ID的随机性
        tArr.sort((a, b) => Math.random() > 0.5 ? 1 : -1)
        // 将数组元素拼接为字符串
        let t: string = tArr.join('');
        // 返回拼接后的字符串作为用户ID
        return t;
    }
    /**解析对应格式数据 */
    public static parseData(mb: string, ty1: string, ty2: string): any {

        let data = []
        let tb = mb.split(ty1)
        if (tb) {
            for (let jjj = 0; jjj < tb.length; jjj++) {
                if (!data[jjj]) {
                    data[jjj] = []
                }
                let str2 = tb[jjj].split(ty2)
                data[jjj].push(...str2)
            }
        }

        return data

    }

    /**时间转化函数 参数：秒 */
    public static formatSeconds = (value: number, formate: Array<string> = ["", ":", ":"], isno: boolean = false) => {
        if (value < 0) {
            return "00:00:00"
        }
        let secondTime: number | string = value;
        let minuteTime: number | string = 0;
        let hourTime: number | string = 0;
        if (secondTime > 60) {
            minuteTime = Math.floor(secondTime / 60);
            secondTime = Math.floor((secondTime % 60))
            if (Number(minuteTime) > 60) {
                hourTime = Math.floor((Number(minuteTime) / 60));
                minuteTime = (Number(minuteTime) % 60);
            }
        } else {
            secondTime = Math.floor(secondTime)
        }
        if (Number(secondTime) === 0) {
            secondTime = '00';
        } else if (Number(secondTime) < 10) {
            secondTime = `0${secondTime}`;
        }
        if (Number(minuteTime) === 0) {
            minuteTime = '00';
        } else if (Number(minuteTime) < 10) {
            minuteTime = `0${minuteTime}`;
        }
        if (Number(hourTime) === 0) {
            hourTime = '00';
        } else if (Number(hourTime) < 10) {
            hourTime = `0${hourTime}`;
        }
        let result = `${hourTime}:${minuteTime}:${secondTime}`;

        if (formate.length == 1) {
            let dw = formate[0]
            result = `${secondTime}${dw}`

        }
        if (formate.length == 2) {
            let dw = formate[0]
            let dw2 = formate[1]
            result = `${minuteTime}${dw2}${secondTime}${dw}`
        }
        if (formate.length == 3) {
            let dw = formate[0]
            let dw2 = formate[1]
            let dw3 = formate[2]
            result = `${hourTime}${dw3}${minuteTime}${dw2}${secondTime}${dw}`
            if (isno && hourTime == "00" && minuteTime != "00") {
                result = `${minuteTime}${dw2}${secondTime}${dw}`
            } else if (isno && hourTime == "00" && minuteTime == "00") {
                result = `${secondTime}${dw}`
            }
        }
        if (result == "00") {
            result = "0"
        }


        return result;
    };

    /**
     * 节点弹出动画（缩放弹出）
     * @param node 目标节点
     * @param duration 动画时长（秒），默认0.3
     * @param scale 目标缩放，默认1
     * @param callback 完成回调
     */
    public static popUp(node: Node, duration: number = 0.3, delay = 0.5, scale: number = 1, callback?: () => void) {
        node.active = true;
        node.setScale(0, 0, 1);
        tween(node)
            .delay(delay)
            .to(duration, { scale: v3(scale, scale, scale) }, { easing: 'backOut' })
            .call(() => { callback && callback(); })
            .start();
    }

    /**
     * 节点呼吸动画（循环缩放）
     * @param node 目标节点
     * @param minScale 最小缩放，默认0.9
     * @param maxScale 最大缩放，默认1.1
     * @param duration 单次呼吸时长（秒），默认0.8
     */
    public static breath(node: Node, minScale: number = 0.9, maxScale: number = 1.1, duration: number = 0.8) {
        tween(node)
            .repeatForever(
                tween()
                    .to(duration, { scale: v3(maxScale, maxScale, maxScale) }, { easing: 'sineInOut' })
                    .to(duration, { scale: v3(minScale, minScale, minScale) }, { easing: 'sineInOut' })
            )
            .start();
    }

    /**
     * 节点淡入动画
     * @param node 目标节点
     * @param duration 动画时长（秒），默认0.3
     * @param callback 完成回调
     */
    public static fadeIn(node: Node, duration: number = 0.3, callback?: () => void) {
        let uiOpacity = node.getComponent(UIOpacity);
        if (!uiOpacity) {
            uiOpacity = node.addComponent(UIOpacity);
        }
        uiOpacity.opacity = 0;
        node.active = true;
        tween(uiOpacity)
            .to(duration, { opacity: 255 })
            .call(() => { callback && callback(); })
            .start();
    }

    /**
     * 节点淡出动画
     * @param node 目标节点
     * @param duration 动画时长（秒），默认0.3
     * @param callback 完成回调
     */
    public static fadeOut(node: Node, duration: number = 0.3, callback?: () => void) {
        let uiOpacity = node.getComponent(UIOpacity);
        if (!uiOpacity) {
            uiOpacity = node.addComponent(UIOpacity);
        }
        tween(uiOpacity)
            .to(duration, { opacity: 0 })
            .call(() => {
                node.active = false;
                callback && callback();
            })
            .start();
    }

    /**
     * 节点抖动动画
     * @param node 目标节点
     * @param duration 动画时长（秒），默认0.2
     * @param strength 抖动强度，默认10
     * @param callback 完成回调
     */
    public static shake(node: Node, duration: number = 0.2, strength: number = 10, callback?: () => void) {
        let originPos = node.position.clone();
        tween(node)
            .by(duration * 0.25, { position: v3(strength, 0, 0) })
            .by(duration * 0.25, { position: v3(-strength * 2, 0, 0) })
            .by(duration * 0.25, { position: v3(strength * 2, 0, 0) })
            .by(duration * 0.25, { position: v3(-strength, 0, 0) })
            .to(0.01, { position: originPos })
            .call(() => { callback && callback(); })
            .start();
    }

    /**
     * 节点旋转动画
     * @param node 目标节点
     * @param angle 旋转角度（度），默认360
     * @param duration 动画时长（秒），默认1
     * @param repeat 是否循环，默认true
     */
    public static rotate(node: Node, angle: number = 360, duration: number = 1, repeat: boolean = true) {
        let t = tween(node)
            .by(duration, { angle: angle });
        if (repeat) {
            t = t.repeatForever();
        }
        t.start();
    }

    /**
     * 节点移动进场动画
     * @param node 目标节点
     * @param fromPos 起始位置
     * @param toPos 目标位置
     * @param duration 动画时长（秒），默认0.3
     * @param callback 完成回调
     */
    public static moveIn(node: Node, fromPos: Vec3, toPos: Vec3, duration: number = 0.3, callback?: () => void) {
        node.setPosition(fromPos);
        node.active = true;
        tween(node)
            .to(duration, { position: toPos }, { easing: 'cubicOut' })
            .call(() => { callback && callback(); })
            .start();
    }

    /**
     * 节点移动退场动画
     * @param node 目标节点
     * @param toPos 目标位置
     * @param duration 动画时长（秒），默认0.3
     * @param callback 完成回调
     */
    public static moveOut(node: Node, toPos: Vec3, duration: number = 0.3, callback?: () => void) {
        tween(node)
            .to(duration, { position: toPos }, { easing: 'cubicIn' })
            .call(() => {
                node.active = false;
                callback && callback();
            })
            .start();
    }

    /**
     * 获取当前场景 Canvas 的尺寸
     */
    private static getCanvasSize(): { width: number, height: number } {
        const scene = director.getScene();
        if (!scene) return { width: 1920, height: 1080 };
        const canvasNode = scene.getChildByName('Canvas');
        if (!canvasNode) return { width: 1920, height: 1080 };
        const uiTrans = canvasNode.getComponent(UITransform) as UITransform | null;
        if (!uiTrans) return { width: 1920, height: 1080 };
        return { width: uiTrans.contentSize.width, height: uiTrans.contentSize.height };
    }

    /**
     * 节点按方向进场动画（自动从场景外指定方向移动到当前位置）
     * @param node 目标节点
     * @param direction 方向：MoveDirection
     * @param duration 动画时长（秒），默认0.3
     * @param callback 完成回调
     */
    public static moveInDir(node: Node, direction: MoveDirection = MoveDirection.Up, duration: number = 0.3, callback?: () => void) {
        const size = this.getCanvasSize();
        const width = size.width || 1920;
        const height = size.height || 1080;
        const toPos = node.position.clone();
        let fromPos = toPos.clone();
        switch (direction) {
            case MoveDirection.Up: fromPos.y += height / 2 + 200; break;
            case MoveDirection.Down: fromPos.y -= height / 2 + 200; break;
            case MoveDirection.Left: fromPos.x -= width / 2 + 200; break;
            case MoveDirection.Right: fromPos.x += width / 2 + 200; break;
            case MoveDirection.LeftUp: fromPos.x -= width / 2 + 200; fromPos.y += height / 2 + 200; break;
            case MoveDirection.RightUp: fromPos.x += width / 2 + 200; fromPos.y += height / 2 + 200; break;
            case MoveDirection.LeftDown: fromPos.x -= width / 2 + 200; fromPos.y -= height / 2 + 200; break;
            case MoveDirection.RightDown: fromPos.x += width / 2 + 200; fromPos.y -= height / 2 + 200; break;
            default: break;
        }
        node.setPosition(fromPos);
        node.active = true;
        tween(node)
            .to(duration, { position: toPos }, { easing: 'cubicOut' })
            .call(() => { callback && callback(); })
            .start();
    }

    /**
     * 节点按方向退场动画（自动从当前位置移动到场景外指定方向）
     * @param node 目标节点
     * @param direction 方向：MoveDirection
     * @param duration 动画时长（秒），默认0.3
     * @param callback 完成回调
     */
    public static moveOutDir(node: Node, direction: MoveDirection = MoveDirection.Up, duration: number = 0.3, callback?: () => void) {
        const size = this.getCanvasSize();
        const width = size.width || 1920;
        const height = size.height || 1080;
        const fromPos = node.position.clone();
        let toPos = fromPos.clone();
        switch (direction) {
            case MoveDirection.Up: toPos.y += height / 2 + 200; break;
            case MoveDirection.Down: toPos.y -= height / 2 + 200; break;
            case MoveDirection.Left: toPos.x -= width / 2 + 200; break;
            case MoveDirection.Right: toPos.x += width / 2 + 200; break;
            case MoveDirection.LeftUp: toPos.x -= width / 2 + 200; toPos.y += height / 2 + 200; break;
            case MoveDirection.RightUp: toPos.x += width / 2 + 200; toPos.y += height / 2 + 200; break;
            case MoveDirection.LeftDown: toPos.x -= width / 2 + 200; toPos.y -= height / 2 + 200; break;
            case MoveDirection.RightDown: toPos.x += width / 2 + 200; toPos.y -= height / 2 + 200; break;
            default: break;
        }
        tween(node)
            .to(duration, { position: toPos }, { easing: 'cubicIn' })
            .call(() => {
                node.active = false;
                callback && callback();
            })
            .start();
    }

    /**
     * 场景切换前的统一清理方法
     */
    public static cleanupBeforeScene() {
        // 1. 停止所有音频
        try {
            const audioMgr = (window as any).AudioManager?.getInstance?.() || (globalThis as any).AudioManager?.getInstance?.();
            if (audioMgr) {
                audioMgr.musicSource?.stop?.();
                audioMgr.stopAllEffects?.();
                audioMgr.clearAllCache?.();
                audioMgr.clearEffectPool?.();
            }
        } catch (e) { /* 忽略异常 */ }

        // 2. 清理全局事件监听
        try {
            const eventMgr = (window as any).EventManager?.instance || (globalThis as any).EventManager?.instance;
            if (eventMgr) {
                eventMgr.offAll?.();
            }
        } catch (e) { /* 忽略异常 */ }

        // 3. 停止所有定时器（如有全局定时器管理器可补充）
        // 4. 停止所有Tween（如有全局Tween管理器可补充）

        // 5. 清理对象池
        try {
            const poolMgr = (window as any).PoolManager?.instance || (globalThis as any).PoolManager?.instance;
            if (poolMgr && poolMgr.pools) {
                for (const key of poolMgr.pools.keys()) {
                    poolMgr.clearPool?.(key);
                }
            }
            // 清理所有泛型对象池
            if (poolMgr && poolMgr.genericPools) {
                for (const key of poolMgr.genericPools.keys()) {
                    poolMgr.clearGenericPool?.(key);
                }
            }
        } catch (e) { /* 忽略异常 */ }

        // 6. 关闭所有弹窗，清理弹窗缓存
        try {
            const popupMgr = (window as any).PopupManager?.instance || (globalThis as any).PopupManager?.instance;
            if (popupMgr) {
                popupMgr.clearCache?.();
            }
        } catch (e) { /* 忽略异常 */ }

        // 7. 释放所有 bundle
        try {
            BundleManager.instance.releaseAllBundles();
        } catch (e) { /* 忽略异常 */ }

        // 8. 释放资源（如有资源引用管理可补充）
        try {
            const resMgr = (window as any).ResourceManager?.instance || (globalThis as any).ResourceManager?.instance;
            if (resMgr) {
                resMgr.releaseAll(); // 或按需调用 releaseByBundle/releaseByPrefix
            }
        } catch (e) { /* 忽略异常 */ }

        // 9. 清理所有物理对象（自动回收到对象池）
        try {
            // PhysicsWorldManager.instance.clearAll(); // 移除此行
        } catch (e) { /* 忽略异常 */ }
    }

    /**
     * 根据父节点 content 区域裁剪子节点透明度，超出区域的子节点设为透明（降低 drawcall）
     * @param parent 父节点（有 UITransform）
     * @param margin 可选，边界外扩像素，默认0
     */
    public static clipChildrenOpacity(parent: Node, margin: number = 0) {
        const parentTrans = parent.getComponent(UITransform);
        if (!parentTrans) return;
        const width = parentTrans.width;
        const height = parentTrans.height;
        const anchorX = parentTrans.anchorX;
        const anchorY = parentTrans.anchorY;
        // 父节点左下角世界坐标
        const parentWorldPos = parent.getWorldPosition();
        // 父节点世界包围盒
        const minX = parentWorldPos.x - width * anchorX - margin;
        const maxX = parentWorldPos.x + width * (1 - anchorX) + margin;
        const minY = parentWorldPos.y - height * anchorY - margin;
        const maxY = parentWorldPos.y + height * (1 - anchorY) + margin;
        parent.children.forEach(child => {
            const childTrans = child.getComponent(UITransform);
            if (!childTrans) return;
            // 子节点世界包围盒
            const cPos = child.getWorldPosition();
            const cW = childTrans.width;
            const cH = childTrans.height;
            const cAnchorX = childTrans.anchorX;
            const cAnchorY = childTrans.anchorY;
            const cMinX = cPos.x - cW * cAnchorX;
            const cMaxX = cPos.x + cW * (1 - cAnchorX);
            const cMinY = cPos.y - cH * cAnchorY;
            const cMaxY = cPos.y + cH * (1 - cAnchorY);
            // 判断是否完全在 content 区域外
            const isOut = cMaxX < minX || cMinX > maxX || cMaxY < minY || cMinY > maxY;
            let uiOpacity = child.getComponent(UIOpacity);
            if (!uiOpacity) uiOpacity = child.addComponent(UIOpacity);
            uiOpacity.opacity = isOut ? 0 : 255;
        });
    }

    public static bezier2(p0: Vec3, p1: Vec3, p2: Vec3, t: number): Vec3 {
        return new Vec3(
            (1 - t) * (1 - t) * p0.x + 2 * (1 - t) * t * p1.x + t * t * p2.x,
            (1 - t) * (1 - t) * p0.y + 2 * (1 - t) * t * p1.y + t * t * p2.y,
            (1 - t) * (1 - t) * p0.z + 2 * (1 - t) * t * p1.z + t * t * p2.z
        );
    }

    public static bezier3(p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, t: number): Vec3 {
        return new Vec3(
            Math.pow(1 - t, 3) * p0.x +
            3 * Math.pow(1 - t, 2) * t * p1.x +
            3 * (1 - t) * t * t * p2.x +
            t * t * t * p3.x,
            Math.pow(1 - t, 3) * p0.y +
            3 * Math.pow(1 - t, 2) * t * p1.y +
            3 * (1 - t) * t * t * p2.y +
            t * t * t * p3.y,
            Math.pow(1 - t, 3) * p0.z +
            3 * Math.pow(1 - t, 2) * t * p1.z +
            3 * (1 - t) * t * t * p2.z +
            t * t * t * p3.z
        );
    }

    /**
    * 播放Spine动画
    * @param n 一个sp.Skeleton实例或者带有sp.Skeleton组件的cc.Node
    * @param aniName 动画名称
    * @param isloop 是否循环播放，默认为true
    * @throws 当传入的n没有sp.Skeleton组件，或者aniName为空字符串时抛出异常
    */
    public static playSpine(n: sp.Skeleton | Node, aniName: string, isloop: boolean = true) {
        if (typeof aniName !== 'string' || aniName.trim() === '') {
            throw new Error('动画名称 非法');
        }

        let _sp: sp.Skeleton | null = null;
        if (n instanceof Node) {
            _sp = n.getComponent(sp.Skeleton);
            if (!_sp) {
                throw new Error('节点上没有 sp.Skeleton 脚本');
            }
        } else if (n instanceof sp.Skeleton) {
            _sp = n;
        } else {
            throw new Error('动画节点 非法');
        }
        _sp.loop = isloop;
        _sp.animation = aniName;
    }

    /**spine动画结束回调 */
    public static spineCallback(n: Node | sp.Skeleton, f: Function) {
        let _sp: sp.Skeleton | null = null;
        if (n instanceof Node) {
            _sp = n.getComponent(sp.Skeleton);
            if (!_sp) {
                throw new Error('节点上没有 sp.Skeleton 脚本');
            }
        } else if (n instanceof sp.Skeleton) {
            _sp = n;
        } else {
            throw new Error('动画节点 非法');
        }
        _sp.setCompleteListener(null);
        _sp.setCompleteListener(() => {
            _sp.setCompleteListener(null);
            f();
        });
    }
    /**spine动画事件回调 */
    public static spineEventCallback(n: Node | sp.Skeleton, f: Function) {
        let _sp: sp.Skeleton | null = null;
        if (n instanceof Node) {
            _sp = n.getComponent(sp.Skeleton);
            if (!_sp) {
                throw new Error('节点上没有 sp.Skeleton 脚本');
            }
        } else if (n instanceof sp.Skeleton) {
            _sp = n;
        } else {
            throw new Error('动画节点 非法');
        }
        _sp.setEventListener(null);
        _sp.setEventListener((i, e) => {
            _sp.setEventListener(null);
            f(e);
        });
    }

    /**播放Animayion */
    playAnimation(n: Node | Animation, name?: string) {
        let _an: Animation | null = null;
        if (n instanceof Node) {
            _an = n.getComponent(Animation);
            if (!_an) {
                throw new Error('节点上没有 Animation 脚本');
            }
        } else if (n instanceof Animation) {
            _an = n;
        } else {
            throw new Error('动作节点 非法');
        }
        _an.play(name);

    }

    /**
     * 添加动画结束事件监听
     * @param animation Animation 组件
     * @param callback 动画结束回调函数
     * @param target 回调函数的 this 上下文
     * @returns 是否添加成功
     */
    public static AnmEndCallback(animation: Animation,callback: () => void,target: any): boolean {
        if (!animation) {
            console.error('Animation component is null');
            return false;
        }

        animation.on(Animation.EventType.FINISHED, callback, target);
        return true;
    }

    /**
     * 用 tween 实现贝塞尔运动（支持暂停/恢复/停止）
     * @param node 要移动的节点
     * @param points 控制点数组（3个为二阶，4个为三阶）
     * @param duration 运动总时长（秒）
     * @param onFinish 结束回调
     * @returns 返回 tween 对象，可 pause/resume/stop
     */
    public static bezierTween(node: Node, points: Vec3[], duration: number, onFinish?: () => void) {
        let t = { value: 0 };
        const start = points[0];
        const update = () => {
            let pos: Vec3;
            if (points.length === 3) {
                pos = this.bezier2(points[0], points[1], points[2], t.value);
            } else if (points.length === 4) {
                pos = this.bezier3(points[0], points[1], points[2], points[3], t.value);
            } else {
                throw new Error('只支持3点（二阶）或4点（三阶）贝塞尔');
            }
            node.setPosition(pos);
        };
        // 初始化位置
        node.setPosition(start);

        const tw = tween(t)
            .to(duration, { value: 1 }, {
                onUpdate: update,
                onComplete: () => {
                    update();
                    onFinish && onFinish();
                }
            })
            .start();

        return tw; // 可用 tw.pause()、tw.resume()、tw.stop()
    }


} 
